﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.Data.Entity;
using System.Linq;
using System.Security;
using System.Text.RegularExpressions;
using System.Web.Hosting;
using System.Web.Profile;
using System.Web.Security;
using Tiscs.Practices.Diagnostics;
using Tiscs.Practices.Web.Providers.Entities;

namespace Tiscs.Practices.Web.Providers
{
    internal static class ProvidersHelper
    {
        #region Helpers

        internal static string GetDefaultApplicationName()
        {
            try
            {
                string applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath;
                if (!string.IsNullOrEmpty(applicationVirtualPath))
                {
                    return applicationVirtualPath;
                }
            }
            catch (SecurityException)
            {
            }
            return "/";
        }

        internal static T GetConfigValue<T>(this NameValueCollection config, string key, Converter<string, T> converter, T defaultValue)
        {
            if (config[key] != null)
            {
                return converter(config[key]);
            }
            else
            {
                return defaultValue;
            }
        }

        #endregion

        #region Validation

        internal static Exception TrimAndValidate(this ParameterValidator<string> validator, ref string value)
        {
            value = value == null ? null : value.Trim();
            return validator.Validate(value);
        }

        internal static bool TrimAndCheckRules(this ParameterValidator<string> validator, ref string value, out Exception exception)
        {
            value = value == null ? null : value.Trim();
            return validator.CheckRules(value, out exception);
        }

        internal static bool TrimAndCheckRules(this ParameterValidator<string> validator, ref string value)
        {
            value = value == null ? null : value.Trim();
            return validator.CheckRules(value);
        }

        internal static ParameterValidator<T> AssertNotEmpty<T>(this ParameterValidator<T> validator) where T : ICollection
        {
            validator.Assert(p => p.Count > 0, n => new ArgumentException(string.Format(Parameter_Can_Not_Be_Empty, n), n));
            return validator;
        }

        internal static ParameterValidator<T> AssertNotNullOrEmpty<T>(this ParameterValidator<T> validator) where T : ICollection
        {
            validator.AssertNotNull().Assert(p => p.Count > 0, n => new ArgumentException(string.Format(Parameter_Can_Not_Be_Empty, n), n));
            return validator;
        }

        internal static ParameterValidator<string> AssertNotEmpty(this ParameterValidator<string> validator)
        {
            validator.Assert(p => p.Length > 0, n => new ArgumentException(string.Format(Parameter_Can_Not_Be_Empty, n), n));
            return validator;
        }

        internal static ParameterValidator<string> AssertNotNullOrEmpty(this ParameterValidator<string> validator)
        {
            validator.AssertNotNull().Assert(p => p.Length > 0, n => new ArgumentException(string.Format(Parameter_Can_Not_Be_Empty, n), n));
            return validator;
        }

        internal static ParameterValidator<string> AssertNotNullOrEmptyIfTrue(this ParameterValidator<string> validator, bool condition)
        {
            if (condition)
            {
                validator.AssertNotNull().Assert(p => p.Length > 0, n => new ArgumentException(string.Format(Parameter_Can_Not_Be_Empty, n), n));
            }
            return validator;
        }

        internal static ParameterValidator<string> AssertMaxLength(this ParameterValidator<string> validator, int maxLength)
        {
            validator.Assert(p => p.Length <= maxLength, n => new ArgumentException(string.Format(Parameter_Too_Long, n, maxLength), n));
            return validator;
        }

        internal static ParameterValidator<string> AssertNoCommas(this ParameterValidator<string> validator)
        {
            validator.Assert(p => !p.Contains(","), n => new ArgumentException(string.Format(Parameter_Can_Not_Contain_Commas, n), n));
            return validator;
        }

        internal static ParameterValidator<string> AssertPasswordMatchRegex(this ParameterValidator<string> validator, Regex regex, int minRequiredNonAlphanumericCharacters)
        {
            validator.Assert(p => regex == null || regex.IsMatch(p), n => new ArgumentException(string.Format(Password_Does_Not_Match_Regular_Expression, n), n))
                .Assert(p => minRequiredNonAlphanumericCharacters == 0 || p.Count(c => !char.IsLetterOrDigit(c)) >= minRequiredNonAlphanumericCharacters, n => new ArgumentException(string.Format(ProvidersHelper.Password_Does_Not_Match_Regular_Expression, n), n));
            return validator;
        }

        internal static ParameterValidator<string> AssertPasswordLength(this ParameterValidator<string> validator, int minLength, int maxLength)
        {
            validator.AssertMaxLength(maxLength).Assert(p => p.Length >= minLength, n => new ArgumentException(string.Format(ProvidersHelper.Password_Too_Short, n, minLength), n));
            return validator;
        }

        #endregion

        #region Messages

        internal const string Unrecognized_Attribute = "Attribute not recognized '{0}'.";

        internal const string Session_Id_Too_Long = "Session id is too long.";

        internal const string Session_not_found = "Session not found.";

        internal const string Invalid_Session_State = "The session state information is invalid and might be corrupted.";

        internal const string Invalid_Hash_Algorithm = "The '{0}' hash algorithm type could not be instantiated.";

        internal const string User_Not_Found = "The user '{0}' was not found.";

        internal const string User_Already_In_Role = "The user '{0}' is already in role '{1}'.";

        internal const string User_Already_Not_In_Role = "The user '{0}' is already not in role '{1}'.";

        internal const string Role_Not_Found = "The role '{0}' was not found.";

        internal const string Role_Already_Exists = "The role '{0}' already exists.";

        internal const string Role_Is_Not_Empty = "This role cannot be deleted because there are users present in it.";

        internal const string Password_Too_Short = "The length of parameter '{0}' needs to be greater than or equal to '{1}'.";

        internal const string Password_Does_Not_Match_Regular_Expression = "The parameter '{0}' does not match the regular expression specified in config file.";

        internal const string Password_Need_More_Non_Alpha_Numeric_Chars = "Non alpha numeric characters in '{0}' needs to be greater than or equal to '{1}'.";

        internal const string Unable_To_Convert_Type_To_String = "Unable to convert type: '{0}' to string.";

        internal const string Not_configured_to_support_password_resets = "This provider is not configured to allow password resets. To enable password reset, set enablePasswordReset to \"true\" in the configuration file.";

        internal const string Provider_Can_Not_Retrieve_Hashed_Password = "Configured settings are invalid: Hashed passwords cannot be retrieved. Either set the password format to a different type, or set enablePasswordRetrieval to false.";

        internal const string Provider_Error = "The Provider encountered an unknown error.";

        internal const string Parameter_Can_Not_Be_Empty = "The parameter '{0}' must not be empty.";

        internal const string Parameter_Too_Long = "The parameter '{0}' is too long: it must not exceed {1} chars in length.";

        internal const string Parameter_Can_Not_Contain_Char = "The parameter '{0}' must not contain char '{1}'.";

        internal const string Parameter_Can_Not_Contain_Commas = "The parameter '{0}' must not contain commas.";

        internal const string Parameter_Array_Empty = "The array parameter '{0}' must not be empty.";

        internal const string Parameter_Duplicate_Array_Element = "The array '{0}' must not contain duplicate values.";

        internal const string Membership_UserNotFound = "The user account has been locked out.";

        internal const string Membership_WrongPassword = "The password supplied is wrong.";

        internal const string Membership_WrongAnswer = "The password answer supplied is wrong.";

        internal const string Membership_InvalidPassword = "The password supplied is invalid. Passwords must conform to the password strength requirements configured for the default provider.";

        internal const string Membership_InvalidQuestion = "The password question supplied is invalid. Note that the current provider configuration requires a valid password question and answer. As a result, a CreateUser overload that accepts question and answer parameters must also be used.";

        internal const string Membership_InvalidAnswer = "The password answer supplied is invalid.";

        internal const string Membership_InvalidEmail = "The email supplied is invalid.";

        internal const string Membership_AccountLockOut = "The user account has been locked out.";

        internal const string Membership_InvalidProviderUserKey = "The provider user key supplied is invalid. It must be of type System.Guid.";

        internal const string Membership_Custom_Password_Validation_Failure = "The custom password validation failed.";

        internal const string Membership_PasswordRetrieval_Not_Supported = "This Membership Provider has not been configured to support password retrieval.";

        #endregion

        #region Entity Extensions

        #region SessionEntity

        internal static DbSet<SessionEntity> GetSessions(this DbContext db)
        {
            return db.Set<SessionEntity>();
        }

        internal static SessionEntity GetSession(this DbContext db, string sessionId)
        {
            return db.GetSessions().Find(sessionId);
        }

        #endregion

        #region ApplicationEntity

        internal static DbSet<ApplicationEntity> GetApplications(this DbContext db)
        {
            return db.Set<ApplicationEntity>();
        }

        internal static ApplicationEntity GetApplication(this DbContext db, string applicationName, bool createIfNotExist = false)
        {
            ApplicationEntity application = db.GetApplications().Where(a => a.ApplicationName.ToLower() == applicationName.ToLower()).FirstOrDefault();
            if (application == null && createIfNotExist)
            {
                application = new ApplicationEntity()
                {
                    ApplicationId = Guid.NewGuid(),
                    ApplicationName = applicationName
                };
                db.GetApplications().Add(application);
            }
            return application;
        }

        #endregion

        #region MembershipEntity

        internal static MembershipUser ToMembershipUser(this MembershipEntity entity, string providerName)
        {
            return new MembershipUser(providerName, entity.User.UserName, entity.UserId, entity.Email,
                entity.PasswordQuestion, entity.Comment, entity.IsApproved, entity.IsLockedOut, entity.CreateDate,
                entity.LastLoginDate, entity.User.LastActivityDate, entity.LastPasswordChangedDate, entity.LastLockoutDate);
        }

        internal static DbSet<MembershipEntity> GetMemberships(this DbContext db)
        {
            return db.Set<MembershipEntity>();
        }

        internal static IQueryable<MembershipEntity> GetMemberships(this DbContext db, string applicationName)
        {
            return db.GetMemberships().Where(m => m.Application.ApplicationName.ToLower() == applicationName.ToLower()).OrderBy(m => m.User.UserName);
        }

        internal static MembershipEntity GetMembership(this DbContext db, string applicationName, string userName)
        {
            return db.GetMemberships(applicationName).Where(m => m.User.UserName.ToLower() == userName.ToLower()).FirstOrDefault();
        }

        internal static MembershipEntity GetMembership(this DbContext db, string applicationName, Guid userId)
        {
            return db.GetMemberships(applicationName).Where(m => m.UserId == userId).FirstOrDefault();
        }

        #endregion

        #region UserEntity

        internal static DbSet<UserEntity> GetUsers(this DbContext db)
        {
            return db.Set<UserEntity>();
        }

        internal static IQueryable<UserEntity> GetUsers(this DbContext db, string applicationName)
        {
            return db.GetUsers().Where(u => u.Application.ApplicationName.ToLower() == applicationName.ToLower()).OrderBy(u => u.UserName);
        }

        internal static UserEntity GetUser(this DbContext db, string applicationName, string userName)
        {
            return db.GetUsers(applicationName).Where(u => u.UserName.ToLower() == userName.ToLower()).SingleOrDefault();
        }

        internal static UserEntity GetUser(this DbContext db, string applicationName, Guid userId)
        {
            return db.GetUsers(applicationName).Where(u => u.UserId == userId).SingleOrDefault();
        }

        #endregion

        #region RoleEntity

        internal static DbSet<RoleEntity> GetRoles(this DbContext db)
        {
            return db.Set<RoleEntity>();
        }

        internal static IQueryable<RoleEntity> GetRoles(this DbContext db, string applicationName)
        {
            return db.GetRoles().Where(r => r.Application.ApplicationName.ToLower() == applicationName.ToLower()).OrderBy(r => r.RoleName);
        }

        internal static RoleEntity GetRole(this DbContext db, string applicationName, string roleName)
        {
            return db.GetRoles(applicationName).Where(r => r.RoleName.ToLower() == roleName.ToLower()).SingleOrDefault();
        }

        #endregion

        #region ProfileEntity

        internal static ProfileInfo ToProfileInfo(this ProfileEntity entity)
        {
            return new ProfileInfo(entity.User.UserName, entity.User.IsAnonymous, entity.User.LastActivityDate.ToLocalTime(), entity.LastUpdatedDate.ToLocalTime(), entity.PropertyNames.Length + entity.PropertyValueBinary.Length + entity.PropertyValueStrings.Length);
        }

        internal static DbSet<ProfileEntity> GetProfiles(this DbContext db)
        {
            return db.Set<ProfileEntity>();
        }

        internal static IQueryable<ProfileEntity> GetProfiles(this DbContext db, string applicationName)
        {
            return db.GetProfiles().Where(p => p.User.Application.ApplicationName.ToLower() == applicationName.ToLower()).OrderBy(p => p.User.UserName);
        }

        internal static IQueryable<ProfileEntity> GetProfiles(this DbContext db, string applicationName, ProfileAuthenticationOption authenticationOption, DateTime inactiveSinceDate, string userNameToMatch = null)
        {
            IQueryable<ProfileEntity> queryable = db.GetProfiles(applicationName);
            switch (authenticationOption)
            {
                case ProfileAuthenticationOption.Anonymous:
                    queryable = queryable.Where(p => p.User.IsAnonymous);
                    break;
                case ProfileAuthenticationOption.Authenticated:
                    queryable = queryable.Where(p => !p.User.IsAnonymous);
                    break;
            }
            if (inactiveSinceDate != DateTime.MaxValue)
            {
                inactiveSinceDate = inactiveSinceDate.ToUniversalTime();
                queryable = queryable.Where(p => p.User.LastActivityDate < inactiveSinceDate);
            }
            if (!string.IsNullOrEmpty(userNameToMatch))
            {
                queryable = queryable.Where(p => p.User.UserName.ToLower().Contains(userNameToMatch.ToLower()));
            }
            return queryable;
        }

        internal static IQueryable<ProfileEntity> GetInactiveProfiles(this DbContext db, string applicationName, ProfileAuthenticationOption authenticationOption, DateTime inactiveSinceDate)
        {
            inactiveSinceDate = inactiveSinceDate.ToUniversalTime();
            IQueryable<ProfileEntity> queryable = db.GetProfiles(applicationName).Where(p => p.LastUpdatedDate < inactiveSinceDate);
            switch (authenticationOption)
            {
                case ProfileAuthenticationOption.Anonymous:
                    queryable = queryable.Where(p => p.User.IsAnonymous);
                    break;
                case ProfileAuthenticationOption.Authenticated:
                    queryable = queryable.Where(p => !p.User.IsAnonymous);
                    break;
            }
            return queryable;
        }

        internal static ProfileEntity GetProfile(this DbContext db, string applicationName, string userName)
        {
            return db.GetProfiles(applicationName).Where(p => p.User.UserName.ToLower() == userName.ToLower()).SingleOrDefault();
        }

        #endregion

        #endregion
    }
}
